//! 高级类型练习
//! https://course.ziglang.cc/basic/advanced_type
//! @author XQ Lin
//! @email xqlin@qq.com
//!

const std = @import("std");
const debug = std.debug.print;

const util = @import("util.zig");
usingnamespace util; // TODO: 当前不起作用

pub fn run() void {
    debug("Zig 高级类型练习\n", .{});

    exam_array();
    exam_vector();
    exam_pointer();

    exam_enum();
    exam_union();
}

fn exam_array() void {
    // NOTE:下面2种初始化数组方式都是正确的
    // const rgba = [_]u8{ 255, 0, 128, 127 };
    const rgba: [4]u8 = .{ 255, 0, 128, 127 };
    debug("brga: {any}\n", .{rgba2bgra(rgba)}); // NOTE: 数组格式化输出要用 any

    // ** & ++ ，都是编译期的。运行期的，则用 std.mem
    const small = [_]i32{ 1, 2, 3 };
    const big = small ** 3;
    debug("array ** 3= {any}\n", .{big});
    const arr1 = [_]i32{ 43, 29 };
    const arr2 = arr1 ++ small;
    debug("arr1 ++ arr2 = {any}\n", .{arr2});

    // 多维数组与for
    const mat3 = [3][3]i32{ // NOTE: 多维数组中，除最后一维，必须指定大小，不能用[_]。
        [_]i32{ 123, 456, 789 },
        [_]i32{ 111, 222, 333 },
        [_]i32{ 0, 1, -1 },
    };
    for (mat3, 0..) |arr, row| {
        for (arr, 0..) |val, col| {
            debug("（{}，{}）= {}\n", .{ row, col, val });
        }
    }

    // 使用函数初始化数组
    const arr3 = [_]i32{plusOne(2)} ** 3;
    util.debugln("{any}", .{arr3});
    // 编译期初始化数组
    const fancy = init: {
        var val: [10]usize = undefined;
        for (&val, 0..) |*pt, i| {
            pt.* = i;
        }
        break :init val;
    };
    util.debugln("编译期初始化：{any}", .{fancy});
}
// 参数和返回值必须指定数组长度和类型
fn rgba2bgra(rgba: [4]u8) [4]u8 {
    // 数组解构
    // const b, g, r, a = rgba;// NOTE: 这种声明是错误的
    const r, const g, const b, const a = rgba;
    return .{ b, g, r, a };
}
fn plusOne(x: i32) i32 {
    return x + 1;
}

fn exam_vector() void {
    util.debugln("向量练习", .{});

    // NOTE: 下面定义一个具体的向量类型？
    const v4 = @Vector(4, i32);
    const a = v4{ 1, 2, 3, 4 };
    const b = v4{ 5, 6, 7, 8 };
    const c = a + b;
    util.debugln("a + b = {any}", .{c});

    // 数组转向量
    var arr = [_]f64{ 1.1, 3.2, 4.5, 6.7 };
    const d: @Vector(4, f64) = arr;
    util.debugln("array -> vector: {any}", .{d});
    // 切片转向量
    const e: @Vector(2, f64) = arr[1..3].*; // NOTE: 切片语法
    util.debugln("slice -> vector: {any}", .{e});
    // @as 向量转数组
    arr = @as([4]f64, d);
    util.debugln("Vector -> Array: {any}", .{arr});

    // @splat
    const scalar: u32 = 3;
    const res: @Vector(4, u32) = @splat(scalar);
    util.debugln("scalar-> Vector: {any}", .{res});

    // @shuffle
    {
        const aa = @Vector(7, u8){ 'o', 'l', 'h', 'e', 'r', 'z', 'w' };

        const mask1 = @Vector(5, i32){ 2, 3, 1, 1, 0 };
        // mask 的长度决定返回的向量的长度
        // 非负整数是从 a 选出指定索引的值（从 0 开始，变大）
        // 对于每个 mask 挑选出来的元素，如果它从 A 或 B 中的选出的值是 undefined，则结果元素也是 undefined。
        // 如果 a 或 b 是 undefined，该变量长度相当于另一个非 undefined 变量的长度。
        // 如果两个向量均是 undefined，则 @shuffle 返回所有元素是 undefined 的向量
        const res1: @Vector(5, u8) = @shuffle(u8, aa, undefined, mask1);
        const str1: []const u8 = @as( //  // 将向量转换为字符串切片
            *const [7]u8, // 目标类型：数组指针
            @ptrCast(&res1) // 强制类型转换
            )[0..5]; // 截取有效长度
        util.debugln("shuffle1: {s}", .{str1}); // 只能是转为字符串切片

        const bb = @Vector(4, u8){ 'w', 'd', '!', 'x' };
        const mask2 = @Vector(6, i32){ -1, 0, 4, 1, -2, -3 };
        // 负整数是从 b 选出指定索引的值（从 -1 开始，变小）。
        const res2: @Vector(6, u8) = @shuffle(u8, aa, bb, mask2);
        const str2: []const u8 = @as(*const [6]u8, @ptrCast(&res2))[0..5];
        util.debugln("shuffle2:{s}", .{str2});
    }

    //@reduce() 使用传入的运算符对向量进行水平按顺序合并（sequential horizontal reduction），最终得到一个标量。
    // 所有的运算符均可用于整型
    // .And, .Or, .Xor 还可用于布尔。
    // .Min, .Max, .Add, .Mul 还可以用于浮点型。
    // 注意：.Add 和 .Mul 在整型上的操作是 wrapping。
    const V4 = @Vector(4, i32);
    const val = V4{ 1, -1, 1, -1 };
    const result = val > @as(V4, @splat(0)); // result 是 { true, false, true, false };
    const is_all_true = @reduce(.And, result); // false
    util.debugln("{any}", .{is_all_true});

    // @select():
    // 根据 pred（元素全为布尔类型的向量）从 a 或 b 中按元素选择值。
    // 如果 pred[i] 为 true，则结果中的相应元素将为 a[i]，否则为 b[i]。
    const v4a = V4{ 1, 2, 3, 4 };
    const v4b = V4{ 5, 6, 7, 8 };
    const pred = @Vector(4, bool){ true, false, false, true };
    const v4c = @select(i32, pred, v4a, v4b);
    util.debugln("select(): {any}", .{v4c});
}

// 指针练习
fn exam_pointer() void {
    // 单项指针
    {
        var integer: i32 = 123;
        const ptr = &integer;
        ptr.* += 2;
        util.debugln("ptr.* = {}", .{integer});
        // 指针切片
        util.debugln("ptr slice: {any}", .{ptr[0..1]});
    }
    // 多项指针
    {
        var array = [_]i32{ 1, 2, 3, 4 };
        // NOTE: 下面ptr 类型为 *[4]i32——这本质是单项指针，指向数组起始位置
        const ptr1 = &array;
        ptr1[2] += 3;
        util.debugln("*[4]i32: {any}", .{ptr1[1..]});
        // NOTE: 下面是多项指针，不能直接索引取值，只能取地址：prt2[2]不是取值，是取了第3项（索引从0开始）的指针。
        const ptr2: [*]i32 = &array;
        ptr2[3] += 3; // 元素值的索引
        util.debugln("[*]i32 -> ptr[i]: {any}", .{ptr2[3]});
        // 切片取的是值
        util.debugln("[*]i32 -> ptr[s..e]: {any}", .{ptr2[1..4]});
        // 切片返回的是地址
        util.debugln("[*]i32 -> ptr[s..]: {any}", .{ptr2[1..]}); // 等价于指向 array[1] 的多元素指针
        // NOTE: 总结如下：
        // 1. ptr2[1..4]（安全）：是一个有限切片（bounded slice），类型为 []i32，表示从索引 1 到 3 的元素
        // 2. ptr2[1..]（不安全）：是一个无限切片（unbounded slice），类型为 [*]i32，表示从索引 1 开始到未知长度的元素

        // 下面表示指针地址也是const，不能修改指向的内容.
        // const ptr: [*]const i32 = &array;
    }
    // 指针转换
    {
        // @ptrCast
        // 一般情况下，应当尽量避免使用 @ptrCast，这会创建一个新的指针，根据通过它的加载和存储操作，可能导致无法检测的非法行为。
        const bytes align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12 };
        const u32_ptr: *const u32 = @ptrCast(&bytes);
        util.debugln("u32*: 0x{x}", .{u32_ptr.*}); //0x12121212

        // 标准库转换
        const u32_val = std.mem.bytesAsSlice(u32, bytes[0..])[0];
        util.debugln("u32_val: 0x{x}", .{u32_val}); //0x12121212.

        // 内置函数转换
        const u32_num = @as(u32, @bitCast(bytes));
        util.debugln("u32_num: 0x{x}", .{u32_num}); //0x12121212.
    }
}

const IsOk = enum {
    ok,
    err,
};

// 可以在u2和Num类型之间任意切换
// 指定底层类型为 u2（值域 0-3）
const Numu2 = enum(u2) {
    zero, // 对应 0
    one, // 对应 1
    two, // 对应 2
    three, // 必须补全所有 u2 可能值（0-3）
};

// 可以覆盖部分或全部标记值
const Numu4 = enum(u4) {
    a,
    b = 8,
    c,
    d = 4,
    e,
    _, //未列出值
};

// 大小为u1
const Suit = enum {
    clubs,
    spades,
    diamonds,
    hearts,

    // 枚举本质是一种命名空间，可以有方法
    pub fn isCube(self: Suit) bool {
        return self == Suit.clubs;
    }
};

fn exam_enum() void {
    const res = IsOk.ok;
    util.debugln("emum: {}", .{res});

    var one = Numu2.one;
    one = .two; // 匿名枚举
    const n = @intFromEnum(one);
    // NOTE: 整数不能超过 u2的范围，否则编译错误。
    one = @enumFromInt(2);
    util.debugln("enum->u2:{}, u2->enum: {}", .{ n, one });

    const cube = Suit.clubs;
    util.debugln("isCube: {}", .{cube.isCube()});

    // 枚举大小是经过编译器严格计算的
    // try std.testing.expect(@typeInfo(Suit).@"enum".tag_type == u2);

    const enumu4 = Numu4.b;
    var isb = switch (enumu4) {
        .a, .c, .d, .e => false, // NOTE: 使用“_”，则必须穷举完enum中已列出的所有选项
        .b => true,
        _ => false,
    };
    util.debugln("is b {}", .{isb});
    isb = switch (enumu4) {
        .b => true,
        else => false, // NOTE: 使用else，不需要全部穷举
    };
    util.debugln("is b: {}", .{isb});
}

const Derp = opaque {};
const Wat = opaque {};
// opaque 类型声明一个具有未知（但非零）大小和对齐方式的新类型，它的内部可以包含与结构、联合和枚举相同的声明。
// 这通常用于保证与不公开结构详细信息的 C 代码交互时的类型安全。
extern fn bar(d: *Derp) void;
fn foo(w: *Wat) callconv(.C) void {
    bar(w);
}

// 联合类型 (union)，它实际上是用户定义的一种特殊的类型，划分出一块内存空间用来存储多种类型，但同一时间只能存储一个类型。
// zig 不保证普通联合类型在内存中的表现形式！如果需要，可以使用 extern union 或者 packed union 来保证它遵守 c 的规则。
const Payload = union {
    int: i64,
    float: f64,
    boolean: bool,
};

// 带标记的联合类型,可以辨别当前存储的类型，易于使用。
// union 是普通的联合类型，它可以存储多种值，但它无法跟踪当前值的类型。
// 普通的联合类型在 ReleaseSmall 和 ReleaseFast 的构建模式下，将无法检测出普通的联合类型的读取错误，
// 例如将一个 u64 存储在一个 union 中，然后尝试将其读取为一个 f64，在程序员的角度看是非法的，但运行确是正常的！
const Result = union(IsOk) { // 使用枚举进行标记
    ok: i32,
    err: void,
};

fn exam_union() void {
    var payload = Payload{ .int = 1234 };
    util.debugln("union.int64: {}", .{payload.int});
    payload = Payload{ .boolean = true };
    util.debugln("union.boolean: {}", .{payload.boolean});
    // @unionInit 初始化一个在编译期已知字段名的联合类型
    payload = @unionInit(Payload, "float", 3.14159);
    util.debugln("union.float: {}", .{payload.float});

    var res = Result{ .ok = 123 };
    if (@as(IsOk, res) == Result.ok) { // 通过 @as 函数将联合类型直接看作声明的枚举来使用（或比较）。
        util.debugln("tag union type: {}", .{true});
    }
    switch (res) {
        Result.ok => |*value| { // 捕获了标记联合值的指针，用于修改值
            value.* += 123;
            util.debugln("tag union value: {}", .{value.*});
        },
        Result.err => unreachable,
    }
}

// NOTE：零位类型
// 使用一个i32的set，value不点内存
const Seti32 = std.AutoHashMap(i32, void).init(std.testing.allocator);
// 整数零位: u0,i0
